package webx

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/xungen/goweb/errno"
	"gitee.com/xungen/goweb/utils"
	"gitee.com/xungen/goweb/yaml"
)

type AppConfig struct {
	RouteConfig
	Id    int
	Name  string
	Path  string
	Route RouteConfig
}

type LogConfig struct {
	RouteConfig
	Path    string
	Level   int
	Maxsize int
}

type RouteConfig struct {
	Port int
	Host string
}

func (obj RouteConfig) String() string {
	return fmt.Sprintf("%s:%d", obj.Host, obj.Port)
}

type Config struct {
	App AppConfig
	Log LogConfig
}

var seqno uint64
var config *Config
var configpath string
var sequencehead string
var remoteconfigmap sync.Map
var confignode yaml.YAMLNode

var mimemap = map[string]string{
	"js":       "text/javascipt",
	"cache":    "text/cache-manifest",
	"appcache": "text/cache-manifest",
	"xml":      "text/xml",
	"css":      "text/css",
	"png":      "image/png",
	"bmp":      "image/bmp",
	"gif":      "image/gif",
	"htm":      "text/html",
	"html":     "text/html",
	"jpg":      "image/jpeg",
	"jpeg":     "image/jpeg",
	"ico":      "image/x-icon",
	"pdf":      "application/pdf",
	"doc":      "application/msword",
	"xls":      "application/vnd.ms-excel",
	"ppt":      "application/vnd.ms-powerpoint",
	"json":     "application/json",
	"docx":     "application/msword",
	"xlsx":     "application/vnd.ms-excel",
	"pptx":     "application/vnd.ms-powerpoint",
	"tar":      "application/x-compressed",
	"tgz":      "application/x-compressed",
	"cab":      "application/x-compressed",
	"rar":      "application/x-compressed",
	"zip":      "application/x-zip-compressed",
	"chm":      "application/octet-stream",
	"dll":      "application/octet-stream",
	"exe":      "application/octet-stream",
	"msi":      "application/octet-stream",
	"ocx":      "application/octet-stream",
}

func GetConfig() *Config {
	return config
}

func GetConfigPath() string {
	return configpath
}

func CreateSequence() string {
	idx := atomic.AddUint64(&seqno, 1) % 100000000
	return sequencehead + fmt.Sprintf("%09d", idx)
}

func ClearRemoteConfig(name string) {
	if name == "" {
		remoteconfigmap.Range(func(key, value any) bool {
			remoteconfigmap.Delete(key)
			return true
		})
	} else {
		remoteconfigmap.Delete(name)
	}
}

func GetContentType(extname string) string {
	extname = strings.TrimLeft(extname, ".")
	extname = strings.ToLower(extname)
	res, ok := mimemap[extname]
	if ok {
		if strings.HasPrefix(res, "text") {
			return res + ";charset=utf-8"
		} else {
			return res
		}
	}
	return GetContentType("htm")
}

func LoadConfig(path string) *Config {
	if config == nil {
		cfg := &Config{}
		if confignode.Load(path) {
			confignode.Object(cfg, true)
			if cfg.App.Host == "" {
				cfg.App.Host = "0.0.0.0"
			}
			if cfg.App.Name == "" {
				cfg.App.Name = "cppweb.go"
			}
			if cfg.App.Path == "" || cfg.App.Path == "." || cfg.App.Path == "./" {
				cfg.App.Path = utils.GetCurrentPath()
			}
			if cfg.Log.Path == "" || cfg.Log.Path == "." || cfg.Log.Path == "./" {
				cfg.Log.Path = utils.GetCurrentPath() + "/log"
			}

			os.Setenv("WEBAPP_ROOT_PATH", cfg.App.Path)
			checkStartSequence(cfg.App.Id)

			configpath = path
			config = cfg

			publish(confignode.Get("app.dir"), false)
			publish(confignode.Get("app.url"), false)
			publish(confignode.Get("app.zip"), true)
		}

		if config == nil {
			panic("load config failed")
		}
	}

	return config
}

func GetRemoteConfig(ctx context.Context, name string) (*yaml.YAMLNode, error) {
	if res, ok := remoteconfigmap.Load(name); ok {
		if cfg, ok := res.(*yaml.YAMLNode); ok {
			return cfg, nil
		} else {
			return nil, res.(error)
		}
	}

	type Result struct {
		Code    int    `json:"code"`
		Content string `json:"content"`
	}

	var host = GetRouteHost()
	if host == nil {
		return nil, errno.SYSERR.Copy("route center config missing")
	}

	var res Result
	var link = fmt.Sprintf("http://%s:%d/confile/sharenote?flag=Q&title=%s", host.Host, host.Port, name)
	if buff := GetHttpResult(ctx, link); len(buff) == 0 {
		return nil, errno.NETERR
	} else if err := json.Unmarshal(buff, &res); err != nil {
		return nil, errno.DATAERR.Copy(err.Error())
	} else if res.Code == errno.NOTFOUND.Code() {
		remoteconfigmap.Store(name, errno.NOTFOUND)
		return nil, errno.NOTFOUND
	} else if res.Code < 0 {
		return nil, errno.ERROR
	} else {
		var cfg yaml.YAMLNode
		cfg.Parse(res.Content)
		remoteconfigmap.Store(name, &cfg)
		return &cfg, nil
	}
}

func getAbsPath(path string) string {
	if filepath.IsAbs(path) {
		return path
	} else {
		return config.App.Path + "/" + path
	}
}

func checkStartSequence(id int) {
	sequencehead = fmt.Sprintf("%03d%03d", id%1000, time.Now().Unix()/60%1000)
}

func publish(node *yaml.YAMLNode, zipfile bool) int {
	num := 0
	if node == nil {
		return num
	}
	for _, v := range node.Children() {
		src := v.Value()
		dest := v.Name()
		if src = os.ExpandEnv(src); len(src) > 0 {
			if zipfile {
				errno.AssertNil(PublishZip(getAbsPath(src), dest))
			} else {
				errno.AssertNil(Publish(getAbsPath(src), dest))
			}
			num++
		}
	}
	return num
}

func tryUpdateConfig(log *LogConfig) {
	if log != nil && config.Log.Host == "" {
		config.Log.Host = log.Host
		config.Log.Port = log.Port
	}
}
